// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <ddk/platform-defs.h>
#include <fuchsia/camera/common/c/fidl.h>
#include <fuchsia/camera/common/llcpp/fidl.h>
#include <fuchsia/hardware/camera/c/fidl.h>
#include <lib/driver-integration-test/fixture.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/vmo.h>
#include <zxtest/zxtest.h>
#include <zircon/syscalls.h>

using driver_integration_test::IsolatedDevmgr;

namespace {

constexpr uint32_t kPageSize = 4096;

// TODO(CAM-43): Replace with sysmem version when available?
zx_status_t Gralloc(fuchsia_camera_common_VideoFormat format, uint32_t num_buffers,
                    fuchsia_sysmem_BufferCollectionInfo* buffer_collection, zx::vmo vmos[64]) {
  // In the future, some special alignment might happen here, or special
  // memory allocated...
  // Simple GetBufferSize.  Only valid for simple formats:
  size_t buffer_size =
      fbl::round_up(format.format.height * format.format.planes[0].bytes_per_row, kPageSize);
  buffer_collection->buffer_count = num_buffers;
  buffer_collection->vmo_size = buffer_size;
  buffer_collection->format.image = format.format;
  zx_status_t status;
  for (uint32_t i = 0; i < num_buffers; ++i) {
    status = zx::vmo::create(buffer_size, 0, &vmos[i]);
    if (status != ZX_OK) {
      return status;
    }
    buffer_collection->vmos[i] = vmos[i].get();
  }
  return ZX_OK;
}

// Integration test for the driver defined in zircon/system/dev/virtual_camera.
// This test code loads the driver into an isolated devmgr and tests behavior.
class VirtualCameraTest : public zxtest::Test {
  void SetUp() override;

 protected:
  IsolatedDevmgr devmgr_;
  fbl::unique_fd fd_;
  zx_handle_t device_handle_;
  fuchsia_sysmem_BufferCollectionInfo info_0_;
  zx::vmo vmos_0_[64];
};

const board_test::DeviceEntry kDeviceEntry = []() {
  board_test::DeviceEntry entry = {};
  entry.vid = PDEV_VID_TEST;
  entry.pid = PDEV_PID_VCAMERA_TEST;
  entry.did = PDEV_DID_TEST_VCAMERA;
  return entry;
}();

void VirtualCameraTest::SetUp() {
  IsolatedDevmgr::Args args;
  args.driver_search_paths.push_back("/boot/driver");
  args.driver_search_paths.push_back("/boot/driver/test");
  args.device_list.push_back(kDeviceEntry);
  zx_status_t status = IsolatedDevmgr::Create(&args, &devmgr_);
  ASSERT_OK(status);

  status = devmgr_integration_test::RecursiveWaitForFile(
      devmgr_.devfs_root(), "sys/platform/11:05:b/virtual_camera", &fd_);
  ASSERT_OK(status);

  status = fdio_get_service_handle(fd_.get(), &device_handle_);
  ASSERT_OK(status);
}

TEST_F(VirtualCameraTest, GetDeviceInfoGetFormatsTest) {
  fuchsia_hardware_camera_DeviceInfo device_info;
  zx_status_t status = fuchsia_hardware_camera_ControlV2GetDeviceInfo(device_handle_, &device_info);
  ASSERT_OK(status);
  EXPECT_EQ(1, device_info.max_stream_count);
  EXPECT_EQ(fuchsia_hardware_camera_CAMERA_OUTPUT_STREAM, device_info.output_capabilities);

  fuchsia_camera_common_VideoFormat formats[16];
  uint32_t total_count;
  uint32_t actual_count;
  int32_t out_status;
  status = fuchsia_hardware_camera_ControlV2GetFormats(device_handle_, 1, formats, &total_count,
                                                       &actual_count, &out_status);
  ASSERT_OK(status);
  auto format = formats[0];
  EXPECT_EQ(640, format.format.width);
  EXPECT_EQ(480, format.format.height);
  EXPECT_EQ(1, format.format.layers);
  EXPECT_EQ(30, format.rate.frames_per_sec_numerator);
  EXPECT_EQ(1, format.rate.frames_per_sec_denominator);
  EXPECT_EQ(1, total_count);
  EXPECT_EQ(1, actual_count);

  zx::eventpair driver_token;
  zx::eventpair stream_token;
  status = zx::eventpair::create(0, &stream_token, &driver_token);
  ASSERT_OK(status);

  zx::channel client_request;
  zx::channel server_request;
  status = zx::channel::create(0, &client_request, &server_request);
  ASSERT_OK(status);
  status = Gralloc(format, 2, &info_0_, vmos_0_);
  ASSERT_OK(status);
  status = fuchsia_hardware_camera_ControlV2CreateStream(
      device_handle_, &info_0_, &format.rate, server_request.release(), driver_token.release());
  ASSERT_OK(status);

  // Not fully implemented yet - this is a sanity check.
  zx_handle_t stream_handle = client_request.release();
  status = fuchsia_camera_common_StreamStart(stream_handle);
  EXPECT_OK(status);

  stream_token.reset();
  zx_signals_t pending;
  zx::time deadline = zx::deadline_after(zx::sec(5));
  zx::channel client = zx::channel(stream_handle);
  ASSERT_OK(client.wait_one(ZX_CHANNEL_PEER_CLOSED, deadline, &pending));
  ASSERT_EQ(pending & ZX_CHANNEL_PEER_CLOSED, ZX_CHANNEL_PEER_CLOSED);
}

}  // namespace
